<!DOCTYPE HTML>
<html>
<head>
<title>tiling</title>
<!--

-->
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<meta content="width=device-width; initial-scale=1.0;  maximum-scale=1.0; user-scalable=0;" name="viewport" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<style>
	body { padding:0px; margin:0px; color: white; background-color: black; }
	input { 
		font-size:16px;
		padding: 0px;
	}
	* {font-family: sans-serif}
	form { padding:0px; margin:0px; }
</style>
<script src="../../lib/canvas.js"></script>
<script>

	var v = new Object();
	var phi = (Math.sqrt(5) + 1) / 2;

	var cartwheels;
	var fresh_cartwheels;
	var seed;
	var seed_a;
	var seed_d;

	var p3_verts_foune;
	var p3_verts;
	var p3_edges;
	var p3_tiles;

	var valid = [];

	var bars;
	var ints;
	var drawn;

	var expand;
	var tangents;
	var cartwheel_r;

	function clear_globals() {
		init_tangents();
		cartwheels = [];
		seed = null;
		fresh_cartwheels = [];

		p3_verts_found = [];
		p3_verts = [];
		p3_edges = [];
		p3_tiles = [];

		bars = [];
		drawn = [];
		ints = [];
		if (null != expand) { clearTimeout(expand); }
	}

	function init() {
		canvas_init();
		window.onresize = on_resize;
		on_resize();
	}
	function console_clear() {
		var c = document.getElementById("console");
		c.innerHTML = '';
	}

	function status_clear() {
		var c = document.getElementById("status");
		c.innerHTML = '';
	}

	function status_update(s) {
		var c = document.getElementById("status");
		var o = "" + s;
		o.replace(/&/, '&amp;');
		o.replace(/</, '&lt;');
		o.replace(/>/, '&gt;');
		o.replace(/'/, '&apos;');
		o.replace(/"/, '&quot;');
		c.innerHTML = o;
	}

	function console_log(s) {
		var c = document.getElementById("console");
		var o = "" + s;
		o.replace(/&/, '&amp;');
		o.replace(/</, '&lt;');
		o.replace(/>/, '&gt;');
		o.replace(/'/, '&apos;');
		o.replace(/"/, '&quot;');
		c.innerHTML += o + '<br />';
	}

	function on_resize() {
		canvas.width = window.innerWidth;
		canvas.height = window.innerHeight - 90;
		ctx.globalCompositeOperation = 'lighter';
		set_viewport(0, 0, 30);
	}

	var middle_c;

	function get_musical_distance(t) {
		var c = (phi + 1) / 2;
		if (0 == t) { return 1 - c; }
		var m = ((t < 0) ? -1 : 1);
		var i = Math.abs(t) - 2;
		middle_c_term(i + 3);
		var d = c * m;
		if (i < 0) { return d; }
		d += m * (middle_c.S[i] + middle_c.L[i] * phi);
		return d;
	}

	function expand_middle_c() {
		// l, s l, l s l, s l l s l,
		if (0 == middle_c.m.length) {
			middle_c.m = ['L', 'S', 'L'];
			middle_c.L = [1, 1, 2];
			middle_c.S = [0, 1, 1];
			middle_c.a = 0;
			middle_c.b = 1;
			return;
		}
		var b = middle_c.m.length;
		var i;
		var t;
		for (i = middle_c.a; i < b; ++i) {
			t = middle_c.m[i];
			middle_c.m.push(t);
			middle_c.L.push(middle_c.L[middle_c.L.length - 1]);
			middle_c.S.push(middle_c.S[middle_c.S.length - 1]);
			++middle_c[t][middle_c[t].length - 1];
		}
		middle_c.a = middle_c.b;
		middle_c.b = b;
	}

	function middle_c_term(n) {
		var t = n;
		if (0 == t) { return 'S'; }
		if (1 == t) { return 'L'; }
		if (t > 1) { t -= 2; }
		else { ++t; t *= -1; }
		if (null == middle_c) { middle_c = {
			m: [],
			L: [],
			S: [],
			a: 0,
			b: 0
		}}
		while (middle_c.m.length < t) {
			expand_middle_c();
		}
		return middle_c.m[t];
	}

	function view_scroll(x, y) {
		//how big is the screen?
		var hx =  v.d.w; //canvas.width / v.z;
		var hy =  v.d.h; //canvas.height / v.z;
		set_viewport(v.x + x * hx / phi, v.y + y * hy / phi, v.z);
	}

	function view_zoom(z) {
		set_viewport(v.x, v.y, v.z * Math.pow(phi, z));
	}

	function set_viewport(x, y, z) {
		status_clear();
		v.x = x;
		v.y = y;
		v.z = z;

		// what are the edges of the viewport ?
		// z is pixels/unit
		var w = 1 + phi + .5 * canvas.width / v.z;
		var h = 1 + phi + .5 * canvas.height / v.z;

		v.l = [{x: v.x - w, y: v.y - h}, {x: v.x - w, y: v.y + h}];
		v.t = [{x: v.x - w, y: v.y - h}, {x: v.x + w, y: v.y - h}];
		v.r = [{x: v.x + w, y: v.y - h}, {x: v.x + w, y: v.y + h}];
		v.b = [{x: v.x - w, y: v.y + h}, {x: v.x + w, y: v.y + h}];

		v.d = new Object;
		v.d.w = w;
		v.d.h = h;
		v.d.l = v.x - v.d.w;
		v.d.t = v.y - v.d.h;
		v.d.r = v.x + v.d.w;
		v.d.b = v.y + v.d.h;

		v.cx = canvas.width / 2;
		v.cy = canvas.height / 2;

		redraw();
	}

	function valid_cartwheel_bar(g, n) { 
		var b = find_bar(g, n);
		// this is a valid bar if it has a long gap on one side
		// and a short gap on the other
		return b.cartwheel_valid; 
	}

	function valid_cartwheel(a) {
		var k = a.join(":");
		if (null != valid[k]) { return valid[k]; }
		for (var i in a) {
			if (! valid_cartwheel_bar(i, a[i])) { 
				valid[k] = false; 
				return false;
			}
		}
		valid[k] = true;
		return true;
	}

	function find_cartwheel(a) {
		var k = a.join(':');
		var o = cartwheels[k];
		if (null == o) {
			if (! valid_cartwheel(a)) { return null; }
			o = {
				k: k,
				draw: 'cartwheel',
				a: a
			};
			cartwheels[k] = o;
			find_cartwheel_verts(o);
			fresh_cartwheels.push(o);
			draw_cartwheel(o);
		}
		return o;
	}

	function orient_cartwheel(c) {

		var o = 'o';
		var b = [];
		for (var i = 0; i < c.bars.length; ++i) {
			var l = Math.abs(c.bars[i][1].d - c.bars[i][0].d);
			var r = Math.abs(c.bars[i][1].d - c.bars[i][2].d);
			o += (l > r ? "0" : "1");
			b[i] = [];
			var m = (l > r);
			for (var j = 0; j < 3; ++j) {
				if (m) { b[i].push(c.bars[i][j]); }
				else { b[i].unshift(c.bars[i][j]); }
			}
		}
		var a = {
			o00000: 0,
			o11000: 1,
			o00001: 2,
			o01111: 3,
			o11100: 4,
			o11111: 5,
			o00111: 6,
			o11110: 7,
			o10000: 8,
			o00011: 9
		};
		var n = a[o];

		a = b;
		b = [];
		for (var i = n; i < n + 5; ++i) {
			b.push(a[(i + n) % 5]);
		}
		c.o = n;
		c.obars = b;
		return;

		var verts = [];
		verts.push(find_intersection(b[0][1], b[4][1]));
		verts.push(find_intersection(b[4][1], b[1][1]));
		verts.push(find_intersection(b[1][1], b[2][1]));
		verts.push(find_intersection(b[2][1], b[3][1]));
		verts.push(find_intersection(b[3][1], b[0][1]));

		for (var i in verts) {draw_quick_dot(verts[i]);}
		var sun = find_intersection(b[0][2], b[4][2]);
		ctx.lineWidth = 2;
		ctx.strokeStyle = "gray";
		draw_segments(scale_points([sun, verts[0]]));
	}

	function get_center_bar(theta) {
		return get_bar(0, 0, theta);
	}

	function get_bar(x, y, theta) {
		return [{x: x, y:y}, {x: x + Math.cos(theta), y: y - Math.sin(theta)}]
	}

	function find_center_bar(g) {
		var theta = (1 + g * 2) * Math.PI / 5;
		return {k: g, theta: theta, bar: get_center_bar(theta)};
	}

	function find_bar(g, n) {
		var k = g + ":" + n;
		var o = bars[k];
		if (null == o) {
			var d = (0 == g % 2) ? 1 : -1;
			var c = find_center_bar(g);
			var md = d * get_musical_distance(n);
			var cv = Math.abs(
				get_musical_distance(n - 1) -
				get_musical_distance(n + 1)
			) < 3;
			var bar = find_parallel_line(c.bar, md);
			o = {
				k: k,
				g: g,
				i: n,
				bar: bar,
				theta: c.theta,
				cartwheel_valid: cv,
				d: md
			};
			o.color = rainbow_numbers(.5 + o.d / Math.PI);
			bars[k] = o;
		}
		return o;
	}

	function find_cartwheel_bars(c) {
		c.bars = [];
		c.color = [0, 0, 0];
		for (var i = 0; i < 5; ++i) {
			c.bars[i] = [
				find_bar(i, c.a[i] - 1),
				find_bar(i, c.a[i]),
				find_bar(i, c.a[i] + 1)
			];
			for (var j in c.bars[i][1].color) {
				c.color[j] += c.bars[i][1].color[j];
			}
		}
		for (var i in c.color) { 
			c.color[i] = Math.floor(c.color[i] / 5); 
		}
		orient_cartwheel(c);
		find_p3_verts(c);
		find_p3_edges(c);
		find_cartwheel_star(c);
	}

	function find_p3_edge(a) {
		ctx.strokeStyle = "rgb(15, 15, 15)";
		ctx.lineWidth = 1;
		var k = [];
		for (var i in a) {k.push(a[i].k)}
		k.sort();
		k = 'p3_edge[' + k.join('][') + ']';
		var o = p3_edges[k];
		if (null == o) {
			o = {
			k: k,
			verts: [],
			tiles: []
			};
			p3_edges[k] = o;
			for (var i in a) {
				o.verts[a[i].k] = a[i];
				a[i].edges[k] = o;
			}
			//draw_segments(scale_points(a));
		}
		return o;
	}

	function find_p3_edges(c) {
		find_p3_edge([c.p3_verts['ots'], c.p3_verts['trp']])
		find_p3_edge([c.p3_verts['trp'], c.p3_verts['ord']])
		find_p3_edge([c.p3_verts['ord'], c.p3_verts['ors']])
		find_p3_edge([c.p3_verts['ors'], c.p3_verts['brp']])
		find_p3_edge([c.p3_verts['brp'], c.p3_verts['obd']])
		find_p3_edge([c.p3_verts['obd'], c.p3_verts['blp']])
		find_p3_edge([c.p3_verts['blp'], c.p3_verts['ols']])
		find_p3_edge([c.p3_verts['ols'], c.p3_verts['old']])
		find_p3_edge([c.p3_verts['old'], c.p3_verts['tlp']])
		find_p3_edge([c.p3_verts['tlp'], c.p3_verts['ots']])
		find_p3_edge([c.p3_verts['ots'], c.p3_verts['itp']])
		find_p3_edge([c.p3_verts['itp'], c.p3_verts['ird']])
		find_p3_edge([c.p3_verts['ird'], c.p3_verts['ors']])
		find_p3_edge([c.p3_verts['itp'], c.p3_verts['ild']])
		find_p3_edge([c.p3_verts['ild'], c.p3_verts['ols']])

		find_p3_edge([c.p3_verts['boat'], c.p3_verts['ild']])
		find_p3_edge([c.p3_verts['boat'], c.p3_verts['ird']])
		find_p3_edge([c.p3_verts['boat'], c.p3_verts['blp']])
		find_p3_edge([c.p3_verts['boat'], c.p3_verts['brp']])

		find_p3_edge([c.p3_verts['ldi'], c.p3_verts['tlp']])
		find_p3_edge([c.p3_verts['ldi'], c.p3_verts['ols']])
		find_p3_edge([c.p3_verts['ldi'], c.p3_verts['itp']])

		find_p3_edge([c.p3_verts['rdi'], c.p3_verts['trp']])
		find_p3_edge([c.p3_verts['rdi'], c.p3_verts['ors']])
		find_p3_edge([c.p3_verts['rdi'], c.p3_verts['itp']])
	}

	function draw_quick_dot(d, r, c) {
		var dot = new_draw_dot();
		dot.clr = ((null == c) ? "white" : c);
		dot.r = ((null == r) ? 2 : r);

		var p = scale_points([d])[0];
		dot.x = p.x;
		dot.y = p.y;
		draw_dot(dot);
	}

	function find_intersection(a, b) {
		var k = [a.k, b.k];
		k.sort();
		k = '[' + k.join('][') + ']';
		var o = ints[k];
		if (null == o) {
			o = get_intersection(a.bar, b.bar);
			o.bars = [];
			o.bars[a.k] = a;
			o.bars[b.k] = b;
			o.k = k;
			ints[k] = o;
		}
		return o;
	}

	function find_cartwheel_verts(c) {
		if (null == c.obars) { find_cartwheel_bars(c); }
		var b = c.obars;
		var verts = [];
		verts.push(find_intersection(b[0][0], b[1][0]));
		verts.push(find_intersection(b[3][0], b[4][0]));
		verts.push(find_intersection(b[1][2], b[2][2]));
		verts.push(find_intersection(b[4][2], b[0][0]));
		verts.push(find_intersection(b[2][0], b[3][0]));
		verts.push(find_intersection(b[0][2], b[1][2]));
		verts.push(find_intersection(b[3][2], b[4][2]));
		verts.push(find_intersection(b[1][0], b[2][0]));
		verts.push(find_intersection(b[4][0], b[0][2]));
		verts.push(find_intersection(b[2][2], b[3][2]));

		c.vert = get_polygon_center(verts);
		c.verts = verts;
		find_cartwheel_inner_decagon(c);
		if (null == cartwheel_r) {
			cartwheel_r = get_distance(c.vert, c.p3_verts['star']);
		}
	}

	function find_cartwheel_inner_decagon(c) {
		var verts = [];
		var b = c.obars;
		verts.push(find_intersection(b[2][2], b[4][0]));
		verts.push(find_intersection(b[4][0], b[1][0]));
		verts.push(find_intersection(b[1][0], b[3][2]));
		verts.push(find_intersection(b[3][2], b[0][2]));
		verts.push(find_intersection(b[0][2], b[2][0]));
		verts.push(find_intersection(b[2][0], b[4][2]));
		verts.push(find_intersection(b[4][2], b[1][2]));
		verts.push(find_intersection(b[1][2], b[3][0]));
		verts.push(find_intersection(b[3][0], b[0][0]));
		verts.push(find_intersection(b[0][0], b[2][2]));

		c.decagon = verts;
	}

	function find_next_bar(a, b) {
		var d = b.i - a.i;
		return find_bar(a.g, b.i + d);
	}

	function find_p3_verts(c) {
		var b = c.obars;
		c.p3_verts = [];
		var verts = [];
		//verts.push(find_intersection(b[2][0], b[4][2]));
		//verts.push(find_intersection(b[4][2], b[1][2]));
		//verts.push(find_intersection(b[1][2], b[3][0]));
		//verts.push(find_intersection(b[3][0], b[0][0]));
		//verts.push(find_intersection(b[0][0], b[2][2]));
		//verts.push(find_intersection(b[2][2], b[4][0]));
		//verts.push(find_intersection(b[4][0], b[1][0]));
		//verts.push(find_intersection(b[1][0], b[3][2]));
		//verts.push(find_intersection(b[3][2], b[0][2]));
		//verts.push(find_intersection(b[0][2], b[2][0]));

		//for (var i in verts) {draw_quick_dot(verts[i]);}
		ctx.fillStyle = "gray";
		//fill_polygon(scale_points(verts));

		c.p3_verts['ird'] = 
			find_deuce_p3_vert([b[4][1], b[1][1], b[2][1]]);
		c.p3_verts['ild'] = 
			find_deuce_p3_vert([b[0][1], b[2][1], b[3][1]]);
		c.p3_verts['ord'] = 
			find_deuce_p3_vert([b[1][2], b[3][0], b[4][2]]);
		c.p3_verts['obd'] = 
			find_deuce_p3_vert([b[2][2], b[4][0], b[0][0]]);
		c.p3_verts['old'] = 
			find_deuce_p3_vert([b[1][0], b[3][2], b[0][2]]);
		c.p3_verts['ots'] = find_sun_p3_vert([
			b[0][2], b[1][1], b[2][0], b[3][1], b[4][2]
		]);
		c.p3_verts['ors'] = find_sun_p3_vert([
			b[0][0], b[1][2], b[2][1], b[3][0], b[4][1]
		]);
		c.p3_verts['ols'] = find_sun_p3_vert([
			b[0][1], b[1][0], b[2][1], b[3][2], b[4][0]
		]);
		c.p3_verts['itp'] = find_point_p3_vert([
			b[0][1], b[4][1], b[3][1], b[1][1]
		]);
		c.p3_verts['trp'] = find_point_p3_vert([
			b[0][1], b[1][2], b[2][0], b[4][2]
		]);
		c.p3_verts['brp'] = find_point_p3_vert([
			b[1][1], b[2][2], b[3][0], b[0][0]
		]);
		c.p3_verts['blp'] = find_point_p3_vert([
			b[3][1], b[2][2], b[1][0], b[4][0]
		]);
		c.p3_verts['tlp'] = find_point_p3_vert([
			b[4][1], b[3][2], b[2][0], b[0][2]
		]);
		c.p3_verts['boat'] = find_boat_p3_vert([
			b[0][0], b[1][1], b[2][1],
			b[3][1], b[4][0], b[2][2]
		]);
		c.p3_verts['rdi'] = find_diamond_p3_vert([
			b[4][2], b[0][1], b[1][1],
			b[4][1], b[2][1], b[3][0], b[1][2]
		]);
		c.p3_verts['ldi'] = find_diamond_p3_vert([
			b[0][2], b[4][1], b[3][1],
			b[0][1], b[2][1], b[1][0], b[3][2]
		]);
	}

	function find_deuce_p3_vert(b) { 
		var k = [];
		for (var i in b) {k.push(b[i].k)}
		k.sort();
		k = "deuce|" + k.join("|");
		var o = p3_verts[k];
		if (null == o) {
			o = {k: k, bars: b, verts: [], edges: [], tiles: []};
			p3_verts[k] = o;
			o.verts.push(find_intersection(b[1], b[2]));
			o.verts.push(find_intersection(b[2], b[0]));
			o.verts.push(find_intersection(b[0], b[1]));

			o.vert = get_intersection(
			get_bar(
				o.verts[1].x, o.verts[1].y,
				(Math.PI + b[2].theta + b[0].theta) / 2
			),
			get_bar(
				o.verts[2].x, o.verts[2].y,
				(Math.PI + b[1].theta + b[0].theta) / 2
			)
			);

			o.x = o.vert.x;
			o.y = o.vert.y;

			ctx.fillStyle = "gray";
			ctx.strokeStyle = "gray";
			//fill_polygon(scale_points(o.verts));
			//draw_quick_dot(o, 1, "gray");
		}
		return o;
	}

	function find_point_p3_vert(b) {
		var k = [];
		for (var i in b) {k.push(b[i].k)}
		k.sort();
		k = "point|" + k.join("|");
		var o = p3_verts[k];
		if (null == o) {
			o = {k: k, bars: b, verts: [], edges: [], tiles: []};
			p3_verts[k] = o;
			o.verts.push(find_intersection(b[0], b[1]));
			o.verts.push(find_intersection(b[1], b[2]));
			o.verts.push(find_intersection(b[2], b[3]));
			o.verts.push(find_intersection(b[3], b[0]));

			o.vert = get_intersection(
			get_bar(
				o.verts[0].x, o.verts[0].y,
				(Math.PI + b[0].theta + b[1].theta) / 2
			),
			get_bar(
				o.verts[3].x, o.verts[3].y,
				(Math.PI + b[3].theta + b[0].theta) / 2
			)
			);
			o.x = o.vert.x;
			o.y = o.vert.y;

			ctx.fillStyle = "gray";
			//fill_polygon(scale_points(o.verts));
			//draw_quick_dot(o, 1, "gray");
		}
		return o;

	}

	function find_diamond_p3_vert(b) {
		var k = [];
		for (var i in b) {k.push(b[i].k)}
		k.sort();
		k = "diamond|" + k.join("|");
		var o = p3_verts[k];
		if (null == o) {
			o = {k: k, bars: b, verts: [], edges: [], tiles: []};
			p3_verts[k] = o;
			o.verts.push(find_intersection(b[0], b[1]));
			o.verts.push(find_intersection(b[1], b[2]));
			o.verts.push(find_intersection(b[2], b[3]));
			o.verts.push(find_intersection(b[3], b[4]));
			o.verts.push(find_intersection(b[4], b[5]));
			o.verts.push(find_intersection(b[5], b[6]));
			o.verts.push(find_intersection(b[6], b[0]));

			o.vert = get_intersection(
			get_bar(
				o.verts[0].x, o.verts[0].y,
				(Math.PI + b[0].theta + b[1].theta) / 2
			),
			get_bar(
				o.verts[1].x, o.verts[1].y,
				(Math.PI + b[1].theta + b[2].theta) / 2
			)
			);
			o.x = o.vert.x;
			o.y = o.vert.y;
			o.t = true;

			ctx.fillStyle = "gray";
			//fill_polygon(scale_points(o.verts));
			//draw_quick_dot(o, 1, "gray");
		}
		return o;
	}

	function find_boat_p3_vert(b) {
		var k = [];
		for (var i in b) {k.push(b[i].k)}
		k.sort();
		k = "boat|" + k.join("|");
		var o = p3_verts[k];
		if (null == o) {
			o = {k: k, bars: b, verts: [], edges: [], tiles: []};
			p3_verts[k] = o;
			o.verts.push(find_intersection(b[0], b[1]));
			o.verts.push(find_intersection(b[1], b[2]));
			o.verts.push(find_intersection(b[2], b[3]));
			o.verts.push(find_intersection(b[3], b[4]));
			o.verts.push(find_intersection(b[4], b[5]));
			o.verts.push(find_intersection(b[5], b[0]));

			o.vert = get_intersection(
			get_bar(
				o.verts[0].x, o.verts[0].y,
				(Math.PI + b[0].theta + b[1].theta) / 2
			),
			get_bar(
				o.verts[1].x, o.verts[1].y,
				(Math.PI + b[1].theta + b[2].theta) / 2
			)
			);
			o.x = o.vert.x;
			o.y = o.vert.y;
			o.t = true;

			ctx.fillStyle = "gray";
			//fill_polygon(scale_points(o.verts));
			//draw_quick_dot(o, 1, "gray");
		}
		return o;
	}

	function find_sun_p3_vert(b) { 
		var k = [];
		for (var i in b) {k.push(b[i].k)}
		k.sort();
		k = "sun|" + k.join("|");
		var o = p3_verts[k];
		if (null == o) {
			o = {k: k, bars: b, verts: [], edges: [], tiles: []};
			p3_verts[k] = o;
			o.verts.push(find_intersection(b[0], b[1]));
			o.verts.push(find_intersection(b[1], b[2]));
			o.verts.push(find_intersection(b[2], b[3]));
			o.verts.push(find_intersection(b[3], b[4]));
			o.verts.push(find_intersection(b[4], b[0]));

			o.vert = get_polygon_center(o.verts);
			o.x = o.vert.x;
			o.y = o.vert.y;

			ctx.fillStyle = "gray";
			//fill_polygon(scale_points(o.verts));
			//draw_quick_dot(o, 1, "gray");
		}
		return o;
	}

	function find_star_p3_vert(b) { 
		var k = [];
		for (var i in b) {k.push(b[i].k)}
		k.sort();
		k = "star|" + k.join("|");
		var o = p3_verts[k];
		if (null == o) {
			o = {k: k, bars: b, verts: [], edges: [], tiles: []};
			p3_verts[k] = o;
			o.verts.push(find_intersection(b[0], b[1]));
			o.verts.push(find_intersection(b[1], b[2]));
			o.verts.push(find_intersection(b[2], b[3]));
			o.verts.push(find_intersection(b[3], b[4]));
			o.verts.push(find_intersection(b[4], b[0]));

			o.vert = get_polygon_center(o.verts);
			o.x = o.vert.x;
			o.y = o.vert.y;
			o.t = true;

			ctx.fillStyle = "gray";
			//fill_polygon(scale_points(o.verts));
			//draw_quick_dot(o, 1, "gray");
		}
		return o;
	}

	function find_cartwheel_star(c) {
		var b = c.obars;
		var sb = find_next_bar(b[2][1], b[2][2]);
		var asb = [
			b[0][0],
			b[1][0],
			sb,
			b[3][0],
			b[4][0]
		];
		var sv = find_star_p3_vert(asb);
		c.p3_verts['star'] = sv;

		var ob = [
			find_next_bar(b[0][1], b[0][0]),
			b[1][1],
			b[2][2],
			b[3][1],
			find_next_bar(b[4][1], b[4][0])
		];

		find_p3_edge([sv, find_deuce_p3_vert([asb[2], asb[3], ob[0]])]);
		find_p3_edge([sv, find_deuce_p3_vert([asb[3], asb[4], ob[1]])]);
		find_p3_edge([sv, find_deuce_p3_vert([asb[4], asb[0], ob[2]])]);
		find_p3_edge([sv, find_deuce_p3_vert([asb[0], asb[1], ob[3]])]);
		find_p3_edge([sv, find_deuce_p3_vert([asb[1], asb[2], ob[4]])]);

	}

	function draw_cartwheel_decagon(c) {
		ctx.lineWidth = 1;
		var clr = [];
		for (var i in c.color) { clr[i] = Math.floor(c.color[i] / 4); }
		clr = 'rgb(' + clr.join(',') + ')';
		ctx.strokeStyle = clr;
		draw_polygon(scale_points(c.verts));
		draw_segments(scale_points([c.verts[0], c.vert]));
	}

	function draw_cartwheel(c) {
		//draw_cartwheel_decagon(c);
		/*
		for (var i in c.bars) {
			draw_bars(c.bars[i], .25);
		}
		*/
		var clr = [];
		for (var i in c.color) { clr[i] = Math.floor(c.color[i] / 2); }
		clr = 'rgb(' + clr.join(',') + ')';

		//draw_quick_dot(c.vert, 2, clr);

		clr = [];
		for (var i in c.color) { clr[i] = Math.floor(c.color[i] / 8); }
		clr = 'rgb(' + clr.join(',') + ')';

		ctx.fillStyle = clr;
		//fill_polygon(scale_points(c.decagon, clr));

		var p = scale_points([c.vert, 
		c.p3_verts['boat'], 
		c.p3_verts['star'], 
		c.p3_verts['ots'],
		c.p3_verts['itp']
		]);
		//draw_quick_dot(c.vert, get_distance(p[0], p[1]), clr);
		draw_quick_dot(c.vert, get_distance(p[0], p[2]), clr);
		//draw_quick_dot(c.vert, get_distance(p[0], p[3]), clr);
		clr = [];
		for (var i in c.color) { clr[i] = Math.floor(c.color[i] / 32); }
		clr = 'rgb(' + clr.join(',') + ')';
		draw_quick_dot(c.p3_verts["star"], get_distance(p[1], p[2]), clr);
		clr = [];
		for (var i in c.color) { clr[i] = Math.floor(c.color[i] / 16); }
		clr = 'rgb(' + clr.join(',') + ')';
		draw_quick_dot(c.p3_verts["ots"], get_distance(p[3], p[4]), clr);
		draw_quick_dot(c.p3_verts["ols"], get_distance(p[3], p[4]), clr);
		draw_quick_dot(c.p3_verts["ors"], get_distance(p[3], p[4]), clr);

	}

	function task_expand(t) {
		if (null != expand) { clearTimeout(expand); }
		expand = setTimeout('expand_cartwheels(' + t + ')', t);
	}

	function cartwheel_inside(c) {
	return null != c &&
		(c.vert.x + cartwheel_r > v.l[0].x) &&
		(c.vert.x - cartwheel_r < v.r[0].x) &&
		(c.vert.y + cartwheel_r > v.t[0].y) &&
		(c.vert.y - cartwheel_r < v.b[0].y) &&
		true;
	}

	function find_p3_tile(v0, v1, v2, v3) {
		var k = [v0.k, v2.k];
		k = 'p3:[' + k.join('][') + ']';
		if (null == p3_tiles[k]) {
			var o = {k: k, verts: [v0, v1, v2, v3], edges: []}
			o.draw = 'p3_tile';
			for (var i = 0; i < 4; ++i) {
				var va = o.verts[i];
				var vb = o.verts[(i + 1) % 4];
				var e = find_p3_edge([va, vb]);
				if (null == e) { return null; }
				e.tiles[k] = o;
				o.edges[e.k] = e;
			}
			//find_p3_type(o);
			//find_p3_dart_vert(o);
			//find_tile_color(o);
			p3_tiles[k] = o;
			ctx.strokeStyle = "rgb(31,31,31)";
			draw_polygon(scale_points(o.verts));
			ctx.fillStyle = "rgb(15, 15, 15)";
			fill_polygon(scale_points(o.verts));
		}
		return p3_tiles[k];
	}

	function find_p3_tiles(vert) {
		//find all p3 tiles surrounding this vert.
		// follow all edges surrounding this vert out two edges
		// all pairs of paths that have a common vert at the end
		// form a tile

		if (! vert.t) {return;}
		p3_verts_found[vert.k] = true;
		var t = [];
		for (var i0 in vert.edges) {
			var e0 = vert.edges[i0];
			for (var j0 in e0.verts) {
				var v1 = e0.verts[j0];
				if (p3_verts_found[v1.k]) { continue; }
				for (var i1 in v1.edges) {
					var e1 = v1.edges[i1];
					if (e1.k == e0.k) { continue; }
					for (var j1 in e1.verts) {
						var v2 = e1.verts[j1];
						if (v2.k == v1.k) { continue; }
						if (p3_verts_found[v2.k]) { continue; }
						if (null == t[v2.k]) { 
							t[v2.k] = []; 
						}
						t[v2.k][v1.k] = [v1, v2]; 
					}
				}
			}
		}

		for (var i in t) {
			var a = [];
			for (var j in t[i]) {
				a.push(t[i][j]);
			}
			if (2 != a.length) { continue; }
			find_p3_tile(vert, a[0][0], a[0][1], a[1][0]);
		}
	}

	function find_cartwheel_p3_tiles(c) {
		find_p3_tiles(c.p3_verts['boat']);
		find_p3_tiles(c.p3_verts['star']);
		find_p3_tiles(c.p3_verts['ldi']);
		find_p3_tiles(c.p3_verts['rdi']);
	}

	function expand_cartwheels(t) {
		if (0 == fresh_cartwheels.length) { return; }
		var n = 1;
		while (n > 0 && fresh_cartwheels.length > 0) {
			--n;
			var c = fresh_cartwheels.shift();
			var d = get_distance(c.vert, v);
			if (d < seed_d) { seed_d = d; seed_a = c.a; }
			if (cartwheel_inside(c)) { 
				find_tangent_cartwheels(c); 
				find_cartwheel_p3_tiles(c);
				/*
				for (var i in c.tangents) {
					if (null == c.tangents[i]) { continue; }
					find_tangent_cartwheels(c.tangents[i]);
					find_cartwheel_p3_tiles(c.tangents[i]);
				}
				*/
			}
		}
		task_expand(t);
	}

	function init_tangents() {
		var t = [
			[-1, -1, 2, 0, -2],
			[0, -2, 1, 1, -2],
			[1, -2, 0, 2, -1],
			[2, -1, -1, 2, 0],
			[2, 0, -2, 1, 1],
			[1, 1, -2, 0, 2],
			[0, 2, -1, -1, 2],
			[-1, 2, 0, -2, 1],
			[-2, 1, 1, -2, 0],
			[-2, 0, 2, -1, -1]
		];
		for (var i = 0; i < 10; ++i) {
			t[i + 10] = [];
			for (var j = 0; j < 5; ++j) {
				t[i + 10][j] = t[i][j];
				if (t[i][j] > 0) { ++t[i + 10][j]; }
				else if (t[i][j] < 0) { --t[i + 10][j]; }
			}
		}
		tangents = t;
	}

	function find_tangent_cartwheels(c) {
		if (null == c) { return []; }
		if (null != c.tangents) { return c.tangents; }
		var t = tangents;
		var r = [];
		var o = [];
		for (var i in t) {
			var a = [];
			for (var j in c.a) {
				a[j] = c.a[j] + t[i][j];
			}
			o[i] = find_cartwheel(a);
		}
		c.tangents = o;
		return o;
	}

	function find_seed_cartwheel(t) {
		if (null != expand) { clearTimeout(expand); }
		if (null == seed) { 
			if (null == seed_a) { 
				seed_a = [0,0,0,0,0]; 
			}
			seed = find_cartwheel(seed_a); 
			seed_d = get_distance(seed.vert, v);
		}
		if (cartwheel_inside(seed)) { return task_expand(10); }
		fresh_cartwheels = [];
		var d = get_distance(seed.vert, v);
		find_tangent_cartwheels(seed);
		while (fresh_cartwheels.length > 0) {
			var c = fresh_cartwheels.shift();
			var cd = get_distance(c.vert, v);
			if (cd < d) {
				seed = c;
				seed_a = c.a;
				d = cd;
				seed_d = cd;
			}
		}
		fresh_cartwheels = [seed];
		expand = setTimeout('find_seed_cartwheel(' + t + ')', t);
	}

	function get_theta(b) {
		var dx = b[1].x - b[0].x;
		var dy = b[1].y - b[0].y;
		return Math.atan2(-dy, dx);
	}

	function find_cartwheels() {
		//draw_cartwheel(find_cartwheel([0, 0, 0, 0, 0]));
		find_seed_cartwheel(10);
	}

	function redraw() {
		console_clear();
		canvas_clear();
		clear_globals();
		find_cartwheels();

		ctx.strokeStyle = "white";
		ctx.lineWidth = 1;

		/*
		for (var i in cartwheels) { 
			draw_cartwheel(cartwheels[i]);
		}
		*/
		//draw_bars(bars, .5);
	}

	function get_midpoint(p1, p2) {
		var o = new Object();
		o.x = (p1.x + p2.x) / 2;
		o.y = (p1.y + p2.y) / 2;
		return o;
	}

	function get_polygon_center(p) {
		var a = [];
		for (var i in p) { a.push(p[i]); }
		var o = {x: 0, y:0};
		for (var i in a) {
			o.x += a[i].x / a.length;
			o.y += a[i].y / a.length;
		}
		return o;
	}

	function get_midpoint_r(r, p1, p2) {
		var dx = (p2.x - p1.x);
		var dy = (p2.y - p1.y);
		var d = Math.sqrt(dx * dx + dy * dy);
		var th = Math.atan2(dy, dx);
		var o = new Object();
		o.x = p2.x + r * d * Math.cos(th);
		o.y = p2.y + r * d * Math.sin(th);
		return o;
	}

	function get_midpoint_d(d, p1, p2) {
		var dx = (p2.x - p1.x);
		var dy = (p2.y - p1.y);
		var th = Math.atan2(dy, dx);
		var o = new Object();
		o.x = p1.x + d * Math.cos(th);
		o.y = p1.y + d * Math.sin(th);
		return o;
	}

	function get_intersection(a, b) {
		//log_bar("a", a);
		//log_bar("b", b);
		var p1 = a[0];
		var p2 = a[1];
		var p3 = b[0];
		var p4 = b[1];

		// p1 and p2 are on one line,
		// p3 and p4 are on the other
		
		var d = (
			(p4.y - p3.y) * (p2.x - p1.x) -
			(p4.x - p3.x) * (p2.y - p1.y)
		);
		if (0 == d) { return null; }

		var u = (
			(p4.x - p3.x) * (p1.y - p3.y) -
			(p4.y - p3.y) * (p1.x - p3.x)
		) / d;

		var o = new Object();
		o.x = p1.x + u * (p2.x - p1.x);
		o.y = p1.y + u * (p2.y - p1.y);

		o.k = ('(' + o.x + ',' + o.y + ')');
		//log_bar("intersects", [o]);
		return o;
	}

	function get_distance(p1, p2) {
		var dx = (p2.x - p1.x);
		var dy = (p2.y - p1.y);
		return Math.sqrt(dx * dx + dy * dy);
	}

	function get_regular_polygon_points(sides, x, y, r, theta) {
		var p = new Array();
		var th;
		var i;
		for (i = 0; i < sides; ++i) {
			th = theta + i * 2 * Math.PI / sides;
			p[i] = new Object();
			p[i].x = x + r * Math.cos(th);
			p[i].y = y - r * Math.sin(th);
		}
		return p;
	}

	function scale_points(p) {
		var o = new Array();
		for (var i in p) {
			o[i] = {
				x: p[i].x * v.z + v.cx - v.x * v.z, 
				y: p[i].y * v.z + v.cy - v.y * v.z
			}
		}
		return o;
	}

	function find_parallel_line(p, r) {

		var dx = (p[0].x - p[1].x);
		var dy = (p[0].y - p[1].y);

		var th = Math.atan2(dy, dx) - Math.PI / 2;

		var o = new Array();
		for (var i  in p) {
			o[i] = {x: p[i].x, y: p[i].y};
			o[i].x += r * Math.cos(th);
			o[i].y += r * Math.sin(th);
		}

		return o;
	}

	function fill_polygon(p) {
		ctx.save();
		ctx.beginPath();
		ctx.moveTo(p[0].x, p[0].y);
		for (i = 1; i < p.length; ++i) {
			ctx.lineTo(p[i].x, p[i].y);
		}
		ctx.closePath();
		ctx.fill();
		ctx.restore();
		return p;
	}

	function draw_polygon(p) {
		ctx.save();
		ctx.beginPath();
		ctx.moveTo(p[0].x, p[0].y);
		for (i = 1; i < p.length; ++i) {
			ctx.lineTo(p[i].x, p[i].y);
		}
		ctx.closePath();
		ctx.stroke();
		ctx.restore();
		return p;
	}

	function draw_bars(b, n) {
		for (var k in b) {
			if (null != drawn[b[k].k]) { continue; } 
			drawn[b[k].k] = true;
			var bar = b[k];
			var clr = rainbow_color(.5 + bar.d / Math.PI);
			ctx.strokeStyle = clr;
			ctx.lineWidth = 1;
			if (null != n) {ctx.lineWidth = n};
			draw_bar(bar.bar);
			ctx.lineWidth = 1.0;
		}
	}

	function draw_segments(b) {
		//log_bar("draw", b);
		ctx.save();
		ctx.beginPath();
		ctx.moveTo(b[0].x, b[0].y);
		for (var i = 1; i < b.length; ++i) {
			ctx.lineTo(b[i].x, b[i].y);
			//log_bar("draw", b);
		}
		ctx.stroke();
		ctx.restore();
	}

	function draw_bar(s) {
		var b = find_view_bar(s);
		if (null == b) {return; }
		b = scale_points(b);
		draw_segments(b);
	}

	function find_view_bar(s) {
		var o = new Array();
		o.push(get_intersection(s, v.l));
		o.push(get_intersection(s, v.t));
		o.push(get_intersection(s, v.r));
		o.push(get_intersection(s, v.b));

		// remove all points that are outside the viewport

		var b = new Array();
		var p;
		while (o.length > 0) {
			p = o.shift();
			if (null == p) { continue; }
			if (p.x < v.l[0].x - .00001) { continue; }
			if (p.x > v.r[0].x + .00001) { continue; }
			if (p.y < v.t[0].y - .00001) { continue; }
			if (p.y > v.b[0].y + .00001) { continue; }
			b.push(p);
			if (2 == b.length) {break; }
		}
		//log_bar("scaled", b);
		if (2 == b.length) {return b;}
		return null;
	}


</script>
</head>
<body onload="init()">
	<canvas id="canvas" class="canvas"  width="320" height="280"></canvas>
		<form action="javascript:" method="get">
			<input type="button" value="&lt" 
				onClick="view_scroll(-1, 0)"/>
			<input type="button" value="&gt" 
				onClick="view_scroll(1, 0)"/>
			<input type="button" value="/\" 
				onClick="view_scroll(0, -1)"/>
			<input type="button" value="\/" 
				onClick="view_scroll(0, 1)"/>
			<input type="button" value="+"
				onClick="view_zoom(1)"/>
			<input type="button" value="-"
				onClick="view_zoom(-1)"/>
			<br />
			<span id="status"></span>
		</form>
	<div id="console"></div>
</body>
</html>

